// asynchronous fifo

`timescale 1ns/1ps

module async_fifo
#(parameter
    DATA_BITS  = 8,
    DEPTH_BITS = 8,
    SHOW_AHEAD = 0,
    RAM_STYLE  = "block"
)
(
    // async clear
    input  wire                 I_rst_n,

    // write
    input  wire                 I_wrclk,
    output wire                 O_wrfull,
    input  wire                 I_wrreq,
    input  wire [DATA_BITS-1:0] I_data,
    output wire [DEPTH_BITS:0]  O_wrusedw,

    // read
    input  wire                 I_rdclk,
    output wire                 O_rdempty,
    input  wire                 I_rdreq,
    output wire [DATA_BITS-1:0] O_q,
    output wire [DEPTH_BITS:0]  O_rdusedw
);
//------------------------Parameter----------------------
localparam
    DEPTH = 1 << DEPTH_BITS;

//------------------------Local signal-------------------
(* ram_style = RAM_STYLE *)
reg  [DATA_BITS-1:0]  mem[0:DEPTH-1];
reg                   full;
reg                   empty;
wire                  full_next;
wire                  empty_next;
reg  [DEPTH_BITS:0]   wraddr_bin;
reg  [DEPTH_BITS:0]   rdaddr_bin;
wire [DEPTH_BITS-1:0] wraddr;
wire [DEPTH_BITS-1:0] rdaddr;
wire [DEPTH_BITS:0]   wraddr_bin_next;
wire [DEPTH_BITS:0]   rdaddr_bin_next;
wire [DEPTH_BITS:0]   wraddr_gray_next;
wire [DEPTH_BITS:0]   rdaddr_gray_next;
reg  [DEPTH_BITS:0]   wraddr_gray_sync0;
reg  [DEPTH_BITS:0]   rdaddr_gray_sync0;
(* ASYNC_REG = "TRUE" *) reg  [DEPTH_BITS:0]   wraddr_gray_sync1, wraddr_gray_sync2;
(* ASYNC_REG = "TRUE" *) reg  [DEPTH_BITS:0]   rdaddr_gray_sync1, rdaddr_gray_sync2;
wire [DEPTH_BITS:0]   wraddr_bin_sync;
wire [DEPTH_BITS:0]   rdaddr_bin_sync;
reg  [DATA_BITS-1:0]  q_buf;
reg  [DEPTH_BITS:0]   wrusedw;
reg  [DEPTH_BITS:0]   rdusedw;

//------------------------Instantiation------------------

//------------------------Body---------------------------
assign O_wrfull  = full;
assign O_rdempty = empty;
assign O_q       = q_buf;
assign O_wrusedw = wrusedw;
assign O_rdusedw = rdusedw;

assign full_next  = (wraddr_gray_next == (rdaddr_gray_sync2 ^ (2'b11 << (DEPTH_BITS - 1))));
assign empty_next = (rdaddr_gray_next == wraddr_gray_sync2);

assign wraddr = wraddr_bin[DEPTH_BITS-1:0];
generate
    if (SHOW_AHEAD) begin : gen_show_ahead_raddr
        assign rdaddr = rdaddr_bin_next[DEPTH_BITS-1:0];
    end
    else begin : gen_normal_raddr
        assign rdaddr = rdaddr_bin[DEPTH_BITS-1:0];
    end
endgenerate

assign wraddr_bin_next = (~full  & I_wrreq)? wraddr_bin + 1'b1 : wraddr_bin;
assign rdaddr_bin_next = (~empty & I_rdreq)? rdaddr_bin + 1'b1 : rdaddr_bin;

assign wraddr_gray_next = wraddr_bin_next ^ (wraddr_bin_next >> 1);
assign rdaddr_gray_next = rdaddr_bin_next ^ (rdaddr_bin_next >> 1);

// gray to bin
assign wraddr_bin_sync[DEPTH_BITS] = wraddr_gray_sync2[DEPTH_BITS];
assign rdaddr_bin_sync[DEPTH_BITS] = rdaddr_gray_sync2[DEPTH_BITS];

genvar i;
generate
for (i = 0; i < DEPTH_BITS; i = i + 1) begin : gen_gray_to_bin
    assign wraddr_bin_sync[i] = wraddr_gray_sync2[i] ^ wraddr_bin_sync[i+1];
    assign rdaddr_bin_sync[i] = rdaddr_gray_sync2[i] ^ rdaddr_bin_sync[i+1];
end
endgenerate

// full, wraddr_bin, wraddr_gray_sync0, rdaddr_gray_sync1, rdaddr_gray_sync2
// @ I_wrclk domain
always @(posedge I_wrclk or negedge I_rst_n) begin
    if (~I_rst_n) begin
        full              <= 1'b0;
        wraddr_bin        <= 1'b0;
        wraddr_gray_sync0 <= 1'b0;
        rdaddr_gray_sync1 <= 1'b0;
        rdaddr_gray_sync2 <= 1'b0;
    end
    else begin
        full              <= full_next;
        wraddr_bin        <= wraddr_bin_next;
        wraddr_gray_sync0 <= wraddr_gray_next;
        rdaddr_gray_sync1 <= rdaddr_gray_sync0;
        rdaddr_gray_sync2 <= rdaddr_gray_sync1;
    end
end

// empty, rdaddr_bin, rdaddr_gray_sync0, wraddr_gray_sync1, wraddr_gray_sync2
// @ I_rdclk domain
always @(posedge I_rdclk or negedge I_rst_n) begin
    if (~I_rst_n) begin
        empty             <= 1'b1;
        rdaddr_bin        <= 1'b0;
        rdaddr_gray_sync0 <= 1'b0;
        wraddr_gray_sync1 <= 1'b0;
        wraddr_gray_sync2 <= 1'b0;
    end
    else begin
        empty             <= empty_next;
        rdaddr_bin        <= rdaddr_bin_next;
        rdaddr_gray_sync0 <= rdaddr_gray_next;
        wraddr_gray_sync1 <= wraddr_gray_sync0;
        wraddr_gray_sync2 <= wraddr_gray_sync1;
    end
end

// mem
always @(posedge I_wrclk) begin
    if (~full & I_wrreq)
        mem[wraddr] <= I_data;
end

// q_buf
generate
    if (SHOW_AHEAD) begin : gen_show_ahead_q
        always @(posedge I_rdclk) begin
            q_buf <= mem[rdaddr];
        end
    end
    else begin : gen_normal_q
        always @(posedge I_rdclk) begin
            if (~empty & I_rdreq)
                q_buf <= mem[rdaddr];
        end
    end
endgenerate

// wrusedw
always @(posedge I_wrclk or negedge I_rst_n) begin
    if (~I_rst_n)
        wrusedw <= 1'b0;
    else
        wrusedw <= wraddr_bin_next - rdaddr_bin_sync;
end

// rdusedw
always @(posedge I_rdclk or negedge I_rst_n) begin
    if (~I_rst_n)
        rdusedw <= 1'b0;
    else
        rdusedw <= wraddr_bin_sync - rdaddr_bin_next;
end

endmodule

// vim:tw=0 ts=4 sw=4 et fdm=marker:
